package com.umpay.util;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.*;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Description：
 * <p>
 * 创建日期：2013-10-8
 * </p>
 * 
 * @version V1.0
 * @author lianglei
 */
public class SignEnc {
	
	
	private static final Logger log = LogManager.getLogger(SignEnc.class);
	
	/**
	 * 默认字符集
	 */
	static String CHARACTERENCODE="gb2312"; 
	/**
	 * 缓存证书数据Map<filename，证书数据>
	 */
	static Map<String, byte[]> fileMap = new ConcurrentHashMap<String, byte[]>();
	
	/**
	 * Description：生成数字签名 
	 * <p>创建人：lianglei ,  2013-10-8  下午04:00:01</p>
	 * <p>修改人：lianglei ,  2013-10-8  下午04:00:01</p>
	 * @param dataString  待签名参数字符串
	 * @return
	 * @throws SignEncException
	 * String 
	 */
	public static String sign(String dataString) throws SignEncException {
		//资源对象
		ResourceBundle rb = null;
		//商户私钥路径
		String merPriKeyPath = null;
		if (dataString == null) {
			throw new SignEncException("the data string to be signed cannot be null");
		}

		try {
			rb = ResourceBundle.getBundle("com.umpay.SignVerProp");
		} catch (MissingResourceException mre) {
			throw new SignEncException("cannot find the SignVerProp.properties");
		}
		try {
			merPriKeyPath = rb.getString("mer.prikey.path");
		} catch (MissingResourceException mre) {
			throw new SignEncException("cannot find the value of mer.prikey.path in property file");
		}

		if ((merPriKeyPath == null) || (merPriKeyPath.trim().length() == 0)) {
			throw new SignEncException("cannot find the value of mer.prikey.path in property file");
		}

		String b64Str = sign(dataString, null, merPriKeyPath);
		
		return b64Str;
	}

	

	/**
	 * Description：签名验证
	 * <p>创建人：lianglei ,  2013-10-8  下午05:07:05</p>
	 * <p>修改人：lianglei ,  2013-10-8  下午05:07:05</p>
	 * @param dataString  待验签参数字符串
	 * @param signString  待校验的数据签名字符串
	 * @return
	 * @throws SignEncException
	 * boolean 
	 */
	public static boolean verify(String dataString, String signString) throws SignEncException{
	 //资源对象
	  ResourceBundle rb = null;
	 //平台公钥路径
	  String platCertPath=null;
    if (dataString == null) {
      throw new SignEncException("the data string to be signed cannot be null");
    }

    try
    {
      rb = ResourceBundle.getBundle("com.umpay.SignVerProp");
    }
    catch (MissingResourceException mre)
    {
      throw new SignEncException("cannot find the SignVerProp.properties");
    }
    try
    {
      platCertPath = rb.getString("plat.cert.path");
    }
    catch (MissingResourceException mre)
    {
     
      throw new SignEncException("cannot find the value of plat.cert.path in property file");
    }
    if ((platCertPath == null) || (platCertPath.trim().length() == 0)) {
      throw new SignEncException("cannot find the value of plat.cert.path in property file"); 
      } 
    
    boolean verifyValue = verify(dataString,signString, null, platCertPath);

	return verifyValue;
    }
	
	   /**
	 * Description：设置编码字符集
	 * <p>创建人：lianglei ,  2013-10-9  下午03:12:09</p>
	 * <p>修改人：lianglei ,  2013-10-9  下午03:12:09</p>
	 * @param CharacterEncode
	 * void 
	 */
	  public static void setCharacterEncode(String CharacterEncode){
		   CHARACTERENCODE=CharacterEncode;
	   }
	   
	   /**
	 * Description：生成签名 
	 * <p>创建人：lianglei ,  2013-10-9  下午03:12:58</p>
	 * <p>修改人：lianglei ,  2013-10-9  下午03:12:58</p>
	 * @param dataString  待签名参数字符串
	 * @param charset     设置字符集
	 * @param keyPath     私钥路径
	 * @return
	 * String 
	 * @throws SignEncException 
	 */
	public static String sign(String dataString, String charset, String keyPath) throws SignEncException{
		if (dataString == null||"".equals(dataString.trim())) {
			throw new SignEncException("the data string to be signed cannot be null");
		}
		if (keyPath == null||"".equals(keyPath.trim())) {
			throw new SignEncException("the keyPath cannot be null");
		}
		byte[] merPrikeyByte =getFileContent(keyPath);
		
		String b64Str = sign(dataString, charset, merPrikeyByte);
		log.info("生成签名时使用的密钥内容字节数组:"+merPrikeyByte);
		return b64Str;
	   }
	
	/**
	 * Description：生成签名
	 * <p>创建人：lianglei ,  2015-1-14  下午04:31:54</p>
	 * <p>修改人：lianglei ,  2015-1-14  下午04:31:54</p>
	 * @param dataString  待签名参数字符串
	 * @param charset     设置字符集
	 * @param keyPath     私钥相对路径
	 * @return
	 * @throws SignEncException
	 * String 
	 */
	public static String signAndRelativePath(String dataString, String charset, String keyPath) throws SignEncException{
		if (dataString == null||"".equals(dataString.trim())) {
			throw new SignEncException("the data string to be signed cannot be null");
		}
		if (keyPath == null||"".equals(keyPath.trim())) {
			throw new SignEncException("the keyPath cannot be null");
		}
		byte[] merPrikeyByte =getFileContentByRelativePath(keyPath);
		
		String b64Str = sign(dataString, charset, merPrikeyByte);
		
		return b64Str;
	   }
	
	   /**
	 * Description：生成签名 
	 * <p>创建人：lianglei ,  2013-10-9  下午03:55:37</p>
	 * <p>修改人：lianglei ,  2013-10-9  下午03:55:37</p>
	 * @param dataString  待签名参数字符串
	 * @param charset     设置字符集
	 * @param keyContent  生成签名时使用的密钥内容字节数组
	 * @return
	 * String 
	 * @throws SignEncException 
	 */
	public static String sign(String dataString, String charset, byte[] keyContent) throws SignEncException{
		if (dataString == null||"".equals(dataString.trim())) {
			throw new SignEncException("the data string to be signed cannot be null");
		}
		if (keyContent == null||keyContent.length==0) {
			throw new SignEncException("the keyContent cannot be null");
		}
		
		PrivateKey pk = getPrivateKey(keyContent);
	
		byte[] signbyte = makeSignByte(dataString, pk,charset);
	
		String b64Str = formateSignByteToString(signbyte);
		
		return b64Str;
	   }
	 
	   /**
	 * Description：签名验证 
	 * <p>创建人：lianglei ,  2013-10-9  下午04:01:36</p>
	 * <p>修改人：lianglei ,  2013-10-9  下午04:01:36</p>
	 * @param dataString   待签名参数字符串
	 * @param signString   待校验的数据签名字符串
	 * @param charset      字符集
	 * @param certPath     公钥路径
	 * @return
	 * @throws SignEncException
	 * boolean 
	 */
	public static boolean verify(String dataString, String signString, String charset, String certPath) throws SignEncException{
	   if (dataString == null||"".equals(dataString.trim())) {
			throw new SignEncException("the data string to be signed cannot be null");
		}
		if (signString == null||"".equals(signString.trim())) {
			throw new SignEncException("the signString cannot be null");
		}
		if (certPath == null||"".equals(certPath.trim())) {
			throw new SignEncException("the certPath cannot be null");
		}
		
		 byte[] certByte = getFileContent(certPath);
		
		 boolean verifyValue = verify(dataString, signString, charset, certByte);
		    
		 return verifyValue;
	   }
	
	/**
	 * Description：使用相对路径进行签名验证
	 * <p>创建人：lianglei ,  2015-1-14  下午05:16:57</p>
	 * <p>修改人：lianglei ,  2015-1-14  下午05:16:57</p>
	* @param dataString   待签名参数字符串
	 * @param signString   待校验的数据签名字符串
	 * @param charset      字符集
	 * @param certPath     相对路径公钥路径
	 * @return
	 * @throws SignEncException
	 * boolean 
	 */
	public static boolean verifyAndRelativePath(String dataString, String signString, String charset, String certPath) throws SignEncException{
	   if (dataString == null||"".equals(dataString.trim())) {
			throw new SignEncException("the data string to be signed cannot be null");
		}
		if (signString == null||"".equals(signString.trim())) {
			throw new SignEncException("the signString cannot be null");
		}
		if (certPath == null||"".equals(certPath.trim())) {
			throw new SignEncException("the certPath cannot be null");
		}
		
		 byte[] certByte = getFileContentByRelativePath(certPath);
		
		 boolean verifyValue = verify(dataString, signString, charset, certByte);
		    
		 return verifyValue;
	   }
	
	   /**
	 * Description：签名验证
	 * <p>创建人：lianglei ,  2013-10-9  下午04:03:35</p>
	 * <p>修改人：lianglei ,  2013-10-9  下午04:03:35</p>
	 * @param dataString   待签名参数字符串
	 * @param signString   待校验的数据签名字符串
	 * @param charset      字符集
	 * @param certContent  验签时使用的公钥内容字节数组
	 * @return
	 * boolean 
	 * @throws SignEncException 
	 */
	public static boolean verify(String dataString, String signString, String charset,  byte[] certContent) throws SignEncException{
	  	if (dataString == null||"".equals(dataString.trim())) {
			throw new SignEncException("the data string to be signed cannot be null");
		}
		if (signString == null||"".equals(signString.trim())) {
			throw new SignEncException("the signString cannot be null");
		}
		if (certContent == null||certContent.length==0) {
			throw new SignEncException("the certContent cannot be null");
		}

		 X509Certificate cert = getCertificateObject(certContent);
		    
		 boolean verifyValue = getVerifyValue(dataString, signString, cert,charset);
		    
		 return verifyValue;
	   }
	   
	/**
	 * Description：获取验证结果
	 * <p>创建人：lianglei ,  2013-10-8  下午05:34:43</p>
	 * <p>修改人：lianglei ,  2013-10-8  下午05:34:43</p>
	 * @param dataString
	 * @param signString
	 * @param cert
	 * @return
	 * @throws SignEncException
	 * boolean 
	 */
	private static boolean getVerifyValue(String dataString, String signString,
			X509Certificate cert,String charset) throws SignEncException {
		 boolean verifyValue=false;
		try {
		  BASE64Decoder base64 = new BASE64Decoder();
		  byte[] signed = base64.decodeBuffer(signString);
		  Signature sig = Signature.getInstance("SHA1withRSA");
		  sig.initVerify(cert);
		  if(charset==null||"".equals(charset.trim())){
			  sig.update(dataString.getBytes(CHARACTERENCODE));
		  }else{
			  sig.update(dataString.getBytes(charset.trim()));
		  }
		 
		  verifyValue= sig.verify(signed);
		} catch (Exception e) {
			 e.printStackTrace();
			 throw new SignEncException("verify failed");
		}
		return verifyValue;
	}



	/**
	 * Description：根据平台公钥生成公钥对象
	 * <p>创建人：lianglei ,  2013-10-8  下午05:28:22</p>
	 * <p>修改人：lianglei ,  2013-10-8  下午05:28:22</p>
	 * @param certByte
	 * @return
	 * @throws SignEncException
	 * X509Certificate 
	 */
	private static X509Certificate getCertificateObject(byte[] certByte)
			throws SignEncException {
		CertificateFactory cf = null;
		X509Certificate cert = null;
		 ByteArrayInputStream bais=null;
		try {
		  cf = CertificateFactory.getInstance("X.509");
		  bais = new ByteArrayInputStream(certByte);
		  cert = (X509Certificate)cf.generateCertificate(bais);
		  bais.close();
		}
		catch (Exception e) {
			e.printStackTrace();
		  throw new SignEncException("load the cert failed");
		}finally{
			if(bais!=null){
				try {
					bais.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				bais=null;
			}
		}
		return cert;
	}



	/**
	 * Description：把signByte转成string 
	 * <p>创建人：lianglei ,  2013-10-8  下午05:01:20</p>
	 * <p>修改人：lianglei ,  2013-10-8  下午05:01:20</p>
	 * @param signbyte
	 * @param b64Str
	 * @return
	 * @throws SignEncException
	 * String 
	 */
	private static String formateSignByteToString(byte[] signbyte )
			throws SignEncException {
		 String b64Str=null;
		try {
			BASE64Encoder base64 = new BASE64Encoder();
			b64Str = base64.encode(signbyte);
		} catch (Exception e) {
			e.printStackTrace();
			throw new SignEncException("base64 generation failed");
		}
		try {
			BufferedReader br = new BufferedReader(new StringReader(b64Str));
			String tmpStr = "";
			String tmpStr1 = "";
			while ((tmpStr = br.readLine()) != null) {
				tmpStr1 = tmpStr1 + tmpStr;
			}
			b64Str = tmpStr1;
		} catch (Exception e) {
			e.printStackTrace();
			throw new SignEncException("BufferedReader failed");
		}
		return b64Str;
	}

	/**
	 * Description：生成加密后的数组
	 * <p>创建人：lianglei ,  2013-10-8  下午04:55:41</p>
	 * <p>修改人：lianglei ,  2013-10-8  下午04:55:41</p>
	 * @param dataString
	 * @param pk
	 * @return
	 * @throws SignEncException
	 * byte[] 
	 */
	private static byte[] makeSignByte(String dataString, PrivateKey pk,String charset)
			throws SignEncException {
		Signature sig = null;
		byte[] sb = (byte[]) null;
		try {
			sig = Signature.getInstance("SHA1withRSA");
			sig.initSign(pk);
			if(charset==null||"".equals(charset.trim())){
				sig.update(dataString.getBytes(CHARACTERENCODE));
			}else{
				sig.update(dataString.getBytes(charset.trim()));
			}
			sb = sig.sign();
		} catch (Exception e) {
			e.printStackTrace();
			throw new SignEncException("sign procedure failed");
		}
		return sb;
	}


	/**
	 * Description：获取根据私钥证书生成PrivateKey对象 
	 * <p>创建人：lianglei ,  2013-10-8  下午04:47:12</p>
	 * <p>修改人：lianglei ,  2013-10-8  下午04:47:12</p>
	 * @param kb
	 * @return
	 * @throws SignEncException
	 * PrivateKey 
	 */
	private static PrivateKey getPrivateKey(byte[] kb) throws SignEncException {
		KeyFactory kf = null;
		PrivateKey pk = null;
		PKCS8EncodedKeySpec peks;
		try {
			peks = new PKCS8EncodedKeySpec(kb);
			kf = KeyFactory.getInstance("RSA");
			pk = kf.generatePrivate(peks);
		} catch (Exception e) {
			e.printStackTrace();
			throw new SignEncException("invalid primary key format");
		}
		return pk;
	}

	/**
	 * Description：把文件内容读取到数据中 
	 * <p>创建人：lianglei ,  2013-10-9  下午03:23:17</p>
	 * <p>修改人：lianglei ,  2013-10-9  下午03:23:17</p>
	 * @param filePath
	 * @return
	 * byte[] 
	 * @throws SignEncException 
	 */
	private static byte[] getFileContent(String filePath) throws SignEncException {
		InputStream in = null;
        byte[] key = null;
        try {
            key = new byte[4096];
            in = SignEnc.class.getClassLoader().getResourceAsStream(filePath); // 读取证书文件
            in.read(key);
        } catch (Exception e) {
        	e.printStackTrace();
			throw new SignEncException("加载证书异常");
        }
        return key;
	}
	

	/**
	 * Description：根据相对路径读取文件
	 * <p>创建人：lianglei ,  2015-1-14  下午04:33:31</p>
	 * <p>修改人：lianglei ,  2015-1-14  下午04:33:31</p>
	 * @param filePath
	 * @return
	 * @throws SignEncException
	 * byte[] 
	 */
	private static byte[] getFileContentByRelativePath(String filePath) throws SignEncException {
		byte[] data=null;
		if (!fileMap.containsKey(filePath)) {
			File f = new File(filePath);
			InputStream in = null;
			try {
				in = new FileInputStream(f);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
			    data=toByteArray(in);
				fileMap.put(filePath, data);
			} else {
				data = fileMap.get(filePath);
			}
		return data;
	}

	private static byte[] toByteArray(InputStream input) throws SignEncException {
	    ByteArrayOutputStream output = new ByteArrayOutputStream();
	    byte[] buffer = new byte[4096];
	    byte[] date=null;
	    int n = 0;
	    try{
	    while (-1 != (n = input.read(buffer))) {
	        output.write(buffer, 0, n);
	    }
	    output.flush();
	    date=output.toByteArray();
	    }catch (Exception e) {
			e.printStackTrace();
			fileMap.clear();
			throw new SignEncException("read file to  byteArray failed");
		}finally{
			try {
				output.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
			output=null;
		}
	    return date;
	}
	
	public static void main(String[] args) {
		 SignEnc s=new SignEnc();
	     try {
			System.out.println(s.signAndRelativePath("2222", "GBK", "com/umpay/testUmpay.key.p8"));
		} catch (SignEncException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}